import java.util.Random;
import java.lang.System;
import java.io.*;

public class Ising
{
	private boolean [] s;
	public int L, d, N;
	private double T, H;
	private int M; // for fast magnetization calculations
	Random R;

	public Ising (int L, int d, double H, double T)
	{
		R = new Random ();
		N = 1;
		for (int i=0; i<d; ++i) N *= L;
		this.L = L;
		this.d = d;
		this.H = H;
		this.T = T;
		s = new boolean [N];
		allSpinsUp (); // Must run some variation of this to initialize M
	}

	public Ising (String filename)
	{
		R = new Random ();
		try
		{
			DataInputStream in = new DataInputStream 
				(new FileInputStream (filename));
			double [] params = new double [4];
			for (int i=0; i<params.length; ++i) params[i] = in.readDouble ();
			L = (int) params[0]; d = (int) params[1];
			H = params[2];       T = params[3];
			
			N = 1;
			for (int i=0; i<d; ++i) N *= L;
			s = new boolean [N];
			
			M = 0;
			for (int i=0; i<N; ++i)
			{	
				byte b = in.readByte ();
				if (b == 1) { ++M; s[i] = true;  }
				else        { --M; s[i] = false; } 
			}
			in.close ();
		} 
		catch (IOException e) 
		{
			System.out.println ("Could not write to "+filename);
		}
	}

	public void setSeed (long s) { R.setSeed (s); }

	public void randomizeSeed () { R.setSeed (System.currentTimeMillis ()); }

	public int getSpin (int site) { return s[site] ? 1 : -1; }
	
	public void setSpin (int site, int val) 
	{
		boolean sval;
		if (val == 1) sval = true;
		else if (val == -1) sval = false;
		else 
		{ 
			System.out.println ("Invalid value for setting spin: "+val);
			return;
		}
		if (s[site] != sval) M += 2*val;
		s[site] = sval;
	}

	public void allSpinsUp () { for (int i=0; i<N; ++i) s[i] = true; M = N; }

	public void flipH () { H = -H; }

	public void upH () { H = (H<0) ? -H : H; }

	public void randomizeSpins ()
	{
		M = 0;
		for (int i=0; i<N; ++i) 
		{
			if (R.nextBoolean ()) { ++M; s[i] = true; }
			else { --M; s[i] = false; }
		}
	}

	public boolean [] getLattice () 
	{ 
		boolean [] s = new boolean [N];
		for (int i=0; i<N; ++i) s[i] = this.s[i];
		return s; 
	}

	private void flipSpin (int site)
	{
		s[site] = !s[site];
		M += 2*getSpin (site);
	}

	private int localSpins (int site) // get the sum of the neighbors
	{
		int tot = 0;
		int L_mult = 1;
		for (int i=0; i<d; ++i)
		{
			tot += getSpin ((site+L_mult)%N) + getSpin ((site-L_mult+N)%N);
			L_mult *= L;
		}
		return tot;
	}

	private double deltaE (int site) // Change in energy if site is flipped
	{
		return 2*getSpin(site)*(localSpins(site)+H);
	}

	private int parity (int site)
	{
		int p = 0;
		int L_mult = 1;
		for (int i=0; i<d; ++i) { p += site/L_mult; L_mult *= L; }
		return p % 2;
	}
	
	private void metropolisUpdate (int site)
	{
		double dE = deltaE (site);
		if (R.nextDouble () < Math.exp(-dE/T)) flipSpin (site);
	}

	private void randomUpdate ()
	{
		for (int i=0; i<N; ++i) metropolisUpdate (R.nextInt (N));
	}

	private void checkerboardUpdate ()
	{
		for (int p=0; p<2; ++p) 
			for (int i=0; i<N; ++i) if (parity(i)==p) metropolisUpdate (i);
	}

	public void update ()
	{
		checkerboardUpdate ();
	}

	private int magnetizationCount ()
	{
		int M = 0;
		for (int i=0; i<N; ++i) M += getSpin (i);
		return M;
	}

	public double magnetization () { return (double) M/N; }

	public double energy () // NOT TESTED
	{
		double interactionEnergy = 0.0;
		for (int i=0; i<N; ++i)
			// No double counting
			interactionEnergy += (parity(i)==0) ? getSpin(i)*localSpins(i) : 0;
		return interactionEnergy/N + magnetization()*H; // /N b/c we normalize m
	}

	public void saveIsing (String filename)
	{
		try
		{
			DataOutputStream out = new DataOutputStream 
				(new FileOutputStream (filename));
			double [] params = {L, d, H, T};
			
			for (int i=0; i<params.length; ++i) out.writeDouble (params[i]);
			for (int i=0; i<N; ++i) out.writeByte (s[i] ? 1 : 0);
			out.close ();
		} 
		catch (IOException e) 
		{
			System.out.println ("Could not write to "+filename);
		}
	}
}
